package br.com.afi.util.log;

import java.text.MessageFormat;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import br.com.afi.util.log.writer.Log4jWriter;
import br.com.afi.util.log.writer.LoggerWriter;


/**
 * Essa classe possui métodos para escrita de log. É possível optar pelo uso de qualquer {@link LoggerWriter}.
 * É recomendado utilizar essa classe para a escrita de logs, já que ela prove um maior desempenho se comparado ao uso de logs diretamente.
 * Internamente essa classe utiliza {@link MessageFormat}, portanto é recomendado utilizar essa classe do seguinte modo:
 * 
 * <code>
 * log.debug("Usuário {0} salvo com sucesso", usuario.getCod());
 * </code>
 * <br>
 * <br>
 * <b>Nunca concatene Strings há uma perda considerável de desempenho, principalmente se o log estiver desabilitado</b>
 * <br>
 * <br>
 * É possível configurar o writer que será utilizado. Basta adicionar o arquivo loggerManager.properties ao seu classpath
 * indicando a classe do writer através da chave writer.class exemplo:<br>
 * <code>
 * writter.class = br.com.afi.util.log.writer.Log4jWriter
 * </code>
 * <br>
 * <br>
 * Se o arquivo loggerManager.properties não for encontrado o {@link Log4jWriter} é utilizado.
 *
 * @see Log4jWriter
 * @author André de Fontana Ignacio
 * @version 1.0
 */
@SuppressWarnings("unchecked")
public class Logger {
	private final LoggerWriter log;
	private static Class<? extends LoggerWriter> configuredWriter;
	private static final String PROPERTIES_FILENAME = "loggerManager"; 
	private static final String CLASS_NAME_PROPERTY = "writer.class";
	
	/**
	 * Carrega o arquivo de propriedades.
	 */
	static
	{
		String writerClassname = null;
		try
		{
			final ResourceBundle properties = ResourceBundle.getBundle( PROPERTIES_FILENAME );
			writerClassname = properties.getString( CLASS_NAME_PROPERTY );
			configuredWriter = ( Class<? extends LoggerWriter> ) Class.forName( writerClassname );
		}
		catch ( ClassNotFoundException e ) 
		{
			throw new RuntimeException( MessageFormat.format( "Classe {0} não encontrada.", writerClassname ),e);
		}
		catch( MissingResourceException ex )
		{
			configuredWriter = Log4jWriter.class;
		}
	}
	
	private static LoggerWriter createConfiguredWriter(Class<?> clazz)
	{
		LoggerWriter writer = null;
		if( configuredWriter != null )
		{
			try 
			{
				writer = configuredWriter.newInstance();
				writer.configure( clazz );
			} 
			catch (Exception e) 
			{
				throw new RuntimeException( MessageFormat.format( "Não foi possível instanciar {0} verifique se a classe possui um construtor padrão.", configuredWriter ) );
			} 
		}
		return writer;
	}
	
	private static LoggerWriter createConfiguredWriter(String name)
	{
		LoggerWriter writer = null;
		if( configuredWriter != null )
		{
			try {
				writer = configuredWriter.newInstance();
				writer.configure( name );
			} catch (Exception e) {
				throw new RuntimeException( MessageFormat.format( "Não foi possível instanciar {0} verifique se a classe possui um construtor padrão.", configuredWriter ) );
			} 
		}
		return writer;
	}
	
	/**
	 * Cria um Logger utilizando {@link Log4jWriter}.
	 * 
	 * @param name Nome do log
	 */
	private Logger( String name )
	{
		this.log = createConfiguredWriter( name );
	}
	
	/**
	 * Cria um Logger utilizando o log configurado em loggerManager.properties. Se nada for informado utiliza o {@link Log4jWriter}.
	 * 
	 * @param clazz Classe
	 */
	private Logger( Class<?> clazz )
	{
		this.log = createConfiguredWriter( clazz );
	}
	
	/**
	 * Cria um Logger.
	 * 
	 * @param name Nome do log
	 */
	private Logger( LoggerWriter log, String name )
	{
		this.log = log;
	}
	
	/**
	 * Cria um Logger utilizando o log configurado em loggerManager.properties. Se nada for informado utiliza o {@link Log4jWriter}.
	 * 
	 * @param clazz Classe
	 */
	public static Logger getLogger( Class<?> clazz )
	{
		return new Logger(clazz);
	}
	
	/**
	 * Cria um Logger utilizando {@link Log4jWriter}.
	 * 
	 * @param name Nome do log
	 */
	public static Logger getLogger( String name )
	{
		return new Logger(name);
	}

	/**
	 * Escreve com o Log em nível de DEBUG. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.debug("Usuário {0} salvo com sucesso", throwable, usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param t Throwable
	 * @param args Argumentos
	 */
	public void debug( Object message, Throwable t, Object... args) 
	{
		log.debug( message, t, args);
	}
	
	/**
	 * Escreve com o Log em nível de DEBUG. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.debug("Usuário {0} salvo com sucesso", usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param args Argumentos
	 */
	public void debug( Object message, Object... args )
	{
		log.debug(message, args);
	}
	
	/**
	 * Escreve com o Log em nível de TRACE. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.debug("Usuário {0} salvo com sucesso", throwable, usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param t Throwable
	 * @param args Argumentos
	 */
	public void trace( Object message, Throwable t, Object... args) 
	{
		log.trace( message, t, args);
	}
	
	/**
	 * Escreve com o Log em nível de TRACE. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.debug("Usuário {0} salvo com sucesso", usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param args Argumentos
	 */
	public void trace( Object message, Object... args )
	{
		log.trace(message, args);
	}

	/**
	 * Escreve com o Log em nível de ERROR. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.error("Usuário {0} salvo com sucesso", throwable, usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param t Throwable
	 * @param args Argumentos
	 */
	public void error( Object message, Throwable t, Object... args ) 
	{
		log.error( message, t, args);
	}

	/**
	 * Escreve com o Log em nível de ERROR. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.error("Usuário {0} salvo com sucesso", usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param args Argumentos
	 */
	public void error(Object message, Object... args) 
	{
		log.error( message, args);
	}
	
	/**
	 * Escreve com o Log em nível de FATAL. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.fatal("Usuário {0} salvo com sucesso", throwable, usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param t Throwable
	 * @param args Argumentos
	 */
	public void fatal(Object message, Throwable t, Object... args) 
	{
		log.fatal(message, t, args);
	}

	/**
	 * Escreve com o Log em nível de FATAL. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.fatal("Usuário {0} salvo com sucesso", usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param args Argumentos
	 */
	public void fatal(Object message, Object... args) 
	{
		log.fatal( message, args );
	}
	
	/**
	 * Escreve com o Log em nível de INFO. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.info("Usuário {0} salvo com sucesso", throwable, usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param t Throwable
	 * @param args Argumentos
	 */
	public void info(Object message, Throwable t, Object... args) 
	{
		log.info( message, t, args );
	}

	/**
	 * Escreve com o Log em nível de INFO. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.info("Usuário {0} salvo com sucesso", usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param args Argumentos
	 */
	public void info(Object message, Object... args) 
	{
		log.info( message, args );
	}

	/**
	 * Escreve com o Log em nível de WARN. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.warn("Usuário {0} salvo com sucesso", throwable, usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param t Throwable
	 * @param args Argumentos
	 */
	public void warn(Object message, Throwable t, Object... args) 
	{
		log.warn( message, t, args );
	}

	/**
	 * Escreve com o Log em nível de WARN. Utilize esse método do mesmo modo que um {@link MessageFormat}:
	 * 
	 * <code>
	 * log.warn("Usuário {0} salvo com sucesso", usuario.getCod());
	 * </code>
	 * 
	 * @param message Mensagem
	 * @param args Argumentos
	 */
	public void warn(Object message, Object... args) 
	{
		log.warn( message, args );
	}
}
